Trivy: вредные советы по скрытию уязвимостей

Привет, Хабр! 

Это что, еще одна статья о Trivy? Кажется, будто ничего нового уже об этом инструменте написать нельзя, а сам сканер внедрен в компаниях даже с низким уровнем зрелости ИБ. Но мы заметили, что большая часть статей в интернете представляет собой развернутое руководство по администрированию. А вот описания внутренней логики его работы нам найти так и не удалось.

Определенные выводы можно сделать после внимательного изучения раздела “Coverage” из официальной документации сканера. Это наводит на мысль: “Очевидно, что Trivy сканирует конфигурационные файлы установщиков и пакетных менеджеров”. Но, как часто подобное бывает в математике, очевидное совсем не очевидно и требует доказательств. Именно поэтому, как выразился один из наших коллег, мы «зареверсим опенсурс» и попытаемся разобраться, как в точности работает сканер Trivy. Материал подготовила Анастасия Березовская, инженер по безопасности процессов разработки приложений в Swordfish Security. 

Внутренние файлы

Сканер Trivy обычно сохраняет свои внутренние файлы в директории, расположенной по пути ~/.cache/trivy. В ней можно найти две основные папки: fanal и db. В db содержится база данных с информацией о найденных уязвимостях в библиотеках, а также ее метаданные, такие как версия БД и время ее последнего обновления. Папка fanal хранит базу данных с результатами анализа образов и файловых систем.

Обе базы данных хранятся в формате boltDB — производительном хранилище на основе «ключ-значение». Для удобного просмотра содержимого можно использовать небольшую утилиту boltbrowser. Этот инструмент позволяет быстро и эффективно просматривать и анализировать сведения, содержащиеся в файлах формата boltDB, что делает работу с результатами сканирования более удобной и наглядной.

Рисунок 1. Пример хранимых данных в fanal.db

В базе данных fanal.db можно найти детализированную информацию о слоях контейнерных образов, используемых пакетах и их версиях. На рисунке 1 представлены данные о конкретном слое: команда, с помощью которой он был сформирован, и информация об операционной системе и установленных в ней пакетах. Внимательное рассмотрение уже позволяет сделать определенные выводы о том, как работает исследуемый инструмент. Но не будем ограничиваться предположениями и перейдем к изучению его исходного кода.

Последовательность исполнения

Сканирование образов — лишь одна из многочисленных функций Trivy, мы сосредоточим внимание именно на ней. Запускаем программу в режиме отладки на простом образе Alpine, устанавливаем брейкпоинты в ключевых точках и наблюдаем за последовательностью выполнения команд. Даже без углубления в детали проектирования можно выделить несколько ключевых этапов работы.

На начальном этапе происходит преобразование строковых аргументов командной строки и создание контекста для последующих шагов выполнения.

На втором этапе устанавливается таймаут на время выполнения, инициализируется встроенная база данных fanal, скачивается база данных уязвимостей db и активируются WASL-модули. Затем выбирается основная функция, которая будет выполняться, в нашем случае — сканирование образа.

На третьем этапе определяются анализаторы, которые не будут использоваться (например анализаторы lock-файлов сборщиков), и выбирается метод получения информации об образе (из архива или образа, локально или удаленно). Затем происходит переход к сканированию объекта (такой многоступенчатый переход между функциями позволяет использовать одинаковую реализацию для различных типов сканируемых объектов).

На четвертом этапе конфигурируется и инициализируется сканер. Передаются параметры цели сканирования (имя образа), флаги сканирования удаленных файлов и зависимостей этапа разработки. Настраиваются типы библиотек (пакеты ОС, языковые пакеты) и сканеры (поиск секретов, уязвимых библиотек), а также параметры для работы самого сканера, включая настройки Rekor, AWS и Docker Host. Сюда же передаются отключенные на втором этапе анализаторы (lock-файлы и некоторые конфигурационные файлы).

После этого Trivy подключается к контейнеру через CRI (например containerd, podman, docker) и получает информацию о контейнере: конфигурацию, информацию о драйверах хранилища (overlay, overlay2 — их используют для эффективного наложения файловых систем друг на друга, что позволяет создавать легкие и переносимые образы контейнеров). Также инициализируются фильтры для пропускаемых в сканировании файлов и директорий, объекты логирования и Rekor клиент.

Именно здесь перечисляются анализаторы и пост-анализаторы (о которых подробнее будет сказано дальше). Анализаторы конфигурационных файлов для поиска секретов на основе регулярных выражений (включающих и исключающих — как, например, с префиксом example). На этом этапе у нас есть объект, который содержит полную информацию об образе и его содержимом, готовый к сканированию на уязвимости и соответствие лицензионным требованиям.

Мы потихоньку приближаемся к основной логике работы сканирования. Но прежде перейдем к пятому этапу, который помогает Trivy быть действительно быстродействующим сканером.

На этом этапе сканер обращается к конфигурационному файлу, полученному из работы с Docker на четвертом этапе. Затем извлекается идентификатор образа и его слоев. Здесь происходит попытка получить удаленный SBOM. В нашем случае его нет, поэтому программа продолжает исполнение.

Далее производится поиск индекса базового слоя. Разработчики заботливо оставили в комментариях пример работы этой функции:

FROM debian:8 RUN apt-get update COPY mysecret / ENTRYPOINT ["entrypoint.sh"] CMD ["somecmd"]

Для этого Dockerfile нужно определить слои в истории сборки, которые принадлежат базовому образу Debian. Чтобы это сделать, в цикле просто ищется первая с конца строка вида:

CMD ["/bin/sh"]

Затем идентификаторы слоев и образа конвертируются в ключи для хранения в базе данных fanal.db. Для каждого слоя также извлекается информация из истории, предоставленной опять же самим Docker, о том, какой командой он был сформирован (как мы видели на рисунке 1).

Для каждого из полученных ключей слоев производится попытка извлечения данных из кеша. Это позволяет Trivy эффективно работать: он отправляет на дальнейшее инспектирование только те слои, которых еще нет в кеше (при условии, что версии схем совпадают). Аналогично происходит и с данными об образе.

После фильтрации уже проанализированных слоев приступаем к инспектированию тех, для которых данных в кеше не нашлось. Инспектирование происходит параллельно, что опять же сказывается на производительности.

На шестом этапе мы видим, зачем отделялся базовый слой. Отличие этого анализа только в том, что для него не осуществляется поиск секретов. Для этого к неиспользуемым анализаторам, определенным на третьем этапе, добавляется тип, отвечающий за поиск секретов.

Сканер “проходится” по файловой системе образа. Перед анализом он пропускает файлы, которые были указаны для пропуска, а также файлы с префиксом .wh. и последующими .wh. сегментами, например /etc/.wh..wh..wh..opq. Для справки, такие файлы создаются в системах overlay (Docker как раз относится к такой среде). Эти файлы являются частью механизма, используемого для реализации операций копирования вверх и вниз. Файлы с префиксом .wh. служат маркерами или заполнителями: они указывают на то, что соответствующий файл или каталог был удален или скрыт в overlay-системе. Эти файлы обычно отсутствуют в фактической файловой системе — они являются артефактами, не предназначенными для прямого доступа или манипулирования пользователями или приложениями. Поэтому Trivy их опускает. Все остальные файлы отправляются на анализ.

В ходе этапов анализа происходит следующее: для каждого файла после предварительной фильтрации определяется, соответствует ли он критериям какого-либо анализатора. Иными словами, мы проводим проверку на совместимость.

Если хотя бы одно из условий для файла выполнено, из него извлекается вся необходимая информация. На этом этапе отбираются файлы, требующие дополнительного анализа после основной обработки. Позднее они также дподвергаются подобной процедуре.

Рисунок 2. Пример выводов в логи обхода файловой системы контейнера

На рисунке 2 иллюстрируется последовательный просмотр файлов Trivy. Причем те, что далее идут с логированием уровня дебаг (дебаг принтстейтмент, да-да!) — это файлы, на которые сработал анализатор с типом secret.

Рисунок 3. Последовательность вызовов для анализа образа контейнера (сокращенная)

Завершающие этапы включают объединение полученных результатов анализа, сохранение их в базе данных fanal.db, а также проверку конфигурационного файла образа с последующим сохранением результатов в кеш. На этом всё, фух!

Работа анализаторов

Анализаторы можно разделить на следующие категории:

  • Анализ версии ОС: эти анализаторы извлекают информацию о версии операционной системы из релизного файла. Хотя эти сведения могут показаться не столь примечательными, они всё же добавляются в таблицу для полноты данных.

  • Анализ зависимостей: эти анализаторы ищут зависимости в файле сборки, метаданных установленных пакетов и бинарных файлах. Таблицу сравнения зависимостей можно найти в официальной документации.

  • Анализ данных о сборке: эти анализаторы извлекают информацию о процессе сборки образа.

  • Анализ файлов метаданных установщиков ОС (например apk, dpkg и т. д.). Ознакомиться с ними можно также в официальной документации.

  • Поиск секретов: анализаторы этой категории ищут потенциально опасные секреты.

группа.№

Имя анализатора

Required

Комментарий

1.1

alpineOSAnalyzer

/etc/alpine-release

Парсит версию ОС из соответствующего файла

1.2

amazonLinuxOSAnalyzer

etc/system-release, usr/lib/system-release

1.3

debianOSAnalyzer

etc/debian_version

1.4

marinerOSAnalyzer

etc/mariner-release

1.5

almaOSAnalyzer

etc/almalinux-release

1.6

centOSAnalyzer

etc/centos-release

1.7

fedoraOSAnalyzer

etc/fedora-release, usr/lib/fedora-release

1.8

oracleOSAnalyzer

usr/lib/fedora-release

1.9

redhatOSAnalyzer

etc/redhat-release

1.10

rockyOSAnalyzer

etc/rocky-release

1.11

osReleaseAnalyzer

etc/os-release,
usr/lib/os-release

1.12

ubuntuESMAnalyzer

var/lib/ubuntu-advantage/status.j